// Copyright (C) Calum Grant 2008

#include <cppscript>
#include "unit_tests.hpp"

#include <vector>
#include <cstring>


bool lex_lt(const var & v1, const var & v2)
{
	return std::lexicographical_compare(v1.begin(), v1.end(), v2.begin(), v2.end());
}


bool lex_equal(const var & v1, const var & v2)
{
	return !lex_lt(v1, v2) && !lex_lt(v2, v1);
}


void test_enumerator(var container, var size)
{
	assert( std::distance( container.begin(), container.end() ) == size.as_int() );

	int n=0;
	foreach( i, container ) ++n;
	assert( size == n );

	foreach( i, reverse(container) ) --n;
	assert( 0 == n );
}


void test_string_container()
{
	// Construction
	var str = "hello";
	var wstr = L"hello";

	assert( string_from(list('a')('b')('c')) == "abc" );
	char cstr[] = "hello";
	assert( string_it(cstr, cstr+5) == "hello" );

	assert( str.impl().shared_var() );
	assert( str.class_name() == "string" );
	assert( wstr.class_name() == "string" );


	// Comparison
	assert( wstr > str );
	assert( str == "hello" );
	assert( str < "hello123" );
	assert( str > "abc" );

	// clone
	var str2 = +str;
	assert( str2 == str );
	str2.clear();
	assert( str2 != str );

	// call
	assert_not_supported( str() );

	// output
	assert( str.as_string() == "hello" );

	// proxy
	assert( str.proxy().as_string() == "hello" );

	// as_bool
	assert( str );
	assert( !var("") );

	// as_int
	assert( var("10").as_int() == 10 );
	assert( var(L"10").as_int() == 10 );

	// as_double
	assert( var("0.5").as_double() == 0.5 );
	assert( var(L"0.5").as_double() == 0.5 );

	// max_args
	assert( str.max_args() == 1 );

	// pickle/unpickle
	assert( unpickle(pickle(str)) == str );

	// test_container_basics( "\01\02\03" );

	// iterators
	assert_sequence_equals( "123", reverse("321") );

	// size
	assert( str.size() == 5 );

	// resize
	str2 = +str;
	str2.resize(10);
	assert( str2 == "hello     " );
	str2.resize(4);
	assert( str2 == "hell" );
	str2.resize(0);
	assert( !str2 );	

	// reserve & capacity
	str2 = "abc";
	str2.impl().reserve(100);
	assert( str2.impl().capacity() >= 100 );

	// keys
	assert( str.keys() == range(0,4) );

	// values
	assert( str.values() == str );

	// The index operator accesses an item in the string
	assert( str[0] == 'h' );

	// Index can also set an item in the string
	str[0] = 'x';
	assert( str == "xello" );
	str[3] = 'm';
	assert( str == "xelmo" );

	// If you set an item outside the range of the string, the string is
	// padded with spaces
	str[10] = 'z';
	assert( str == "xelmo     z" );

	str[-2] = 'a';
	assert( str[-1] == 'z' );
	assert( str == "xelmo    az" );
	assert_throws( not_found, str[-40]=' ' );

	// If you get an attrbute that's out of range, you get a null
	str = "123";
	assert( str[3] == ' ' );
	assert( str[-3] == '1' );
	assert( str[-4] == ' ' );

	// You can iterate the contents of the string in a C++ style.
	var end = str.end();
	std::string text;
	for(var::iterator i=str.begin(); i!=end; ++i)
	{
		text += i->as_int();
	}

	assert( str == text );

	// You can also iterate the contents in enumerator style
	var t="";
	foreach( ch, "abcd" ) t.push_back(ch);
	assert(t == "abcd");

	assert( t[0].class_name() == "char" );
	assert( var((char)100) != 100 );
	assert( var(t[1]) == var('b') );

	// insert
	str = "hello";
	str.insert(0,"abc");
	assert( str == "abchello" );
	str = "abc";
	str.insert( 4, "de" );
	assertx( str == "abc de" );
	str = "xy";
	str.insert(1,"");
	assert( str == "xy" );
	str.insert(-3, "ab");
	assert( str == "ab xy" );

	// erase
	str = "abc";
	str.erase( range(1,1) );
	assert( str == "ac" );
	str.erase( range_ex(0,0) );
	assert( str == "ac" );
	str.erase( range_ex( -10, 10) );
	assert( str == "ac" );
	str.erase( range_ex(-10,-8) );
	assert( str == "ac" );
	str.erase( str.keys() );
	assert( !str );

	// String operators
	str = "abc";
	assert( str+"def" == "abcdef" );
	str += "xyz";
	assert( str == "abcxyz" );
	str = "ab";
	assert( str * 2 == "abab" );
	str *= 2;
	assert( str == "abab" );
	str *= 0;
	assert( !str );

	// clear
	str = "abc";
	str.clear();
	assert( !str );

	// contains
	str = "abc";
	assert( str.contains(0) );
	assert( str.contains(-1) );
	assert( str.contains(2) );
	assert( !str.contains(3) );
	assert( !str.contains(-4) );

	// push_front
	assert_not_supported( str.push_front('a') );

	// pop_front
	assert_not_supported( str.pop_front() );
}


void test_op_lt()
{
	var array1 = array();

	var object1 = object();

	std::vector<var> ascending;
	ascending.push_back(null);
	ascending.push_back(false);
	ascending.push_back(true);
	ascending.push_back(-3);
	ascending.push_back(3);
	ascending.push_back(2.0);
	ascending.push_back(2.1);
	ascending.push_back("a");
	ascending.push_back("aa");
	ascending.push_back(L"a");
	ascending.push_back(L"aa");
	ascending.push_back(array1);
	ascending.push_back(object1);

	for(std::vector<var>::iterator i=ascending.begin(); i!=ascending.end(); ++i)
	{
		assert( *i == *i );
		for(std::vector<var>::iterator j=i+1; j!=ascending.end(); ++j)
			assert( *i < *j );
	}

}


void test_object_container()
{
	// Create an object
	var x = object();
		x["b"] = 2;
		x["a"] = 1;

	// shared
	assert( object().impl().shared_var() );

	// compare
	assert( x == x );
	assert( var(1) < x );
	assert( x != object() );

	// comparison_index
	assert( object().impl().comparison_index() == cmp_object );

	var y = +x;
	assert( y != x );
	assert( y.size() == 2 );

	// class_name
	assert( object().class_name() == "" );
	assert( object("hello").class_name() == "hello" );

	// call
	assert_not_supported( x() );
	assert_not_supported( x(12,13) );
	assert( x.max_args() == -1 );

	// output
	assertx( object("hello").as_string() == "hello" );
	assertx( x.as_int() == 0 );
	assertx( x.as_double() == 0.0 );

	// as_bool
	assert( !object() );
	assert( var(object().extend("a",1)) );

	// begin() returns an iterator to the first item.
	var i = x.begin(); 

	// Dereferencing returns the "key", i.e. it isn't a pair like C++
	assert( *i == "a" );

	// Access the key (first item of the pair)
	assert( i.key() == "a" );

	// Access the value (second item of the pair);
	assert( i.value() == 1 );

	// Increment the iterator
	++i;

	// Check next item
	assert( *i == "b" );
	assert( i.key() == "b" );
	assert( i.value() == 2 );

	// Check end
	++i;
	assert( i == x.end() );

	// Test erase()
	x = object().extend("a",1)("b",2);
	x.erase("a");
	assert( x.contains("b") );
	assert( !x.contains("a") );
	assert( !x.contains("c") );

	// iterator invalidation
	assert_throws( expired_iterator, foreach( i, x ) x["p"] = 1 );

	// enumerator
	x = object().extend("a",12)("b","hello");
	var str="";
	foreach(i, x) str += x[i];
	assertx( str == "12hello" );

	// reverse_enumerator
	str="";
	foreach(i, reverse(x)) str += x[i];
	assertx( str == "hello12" );

	// size
	assertx( x.size() == 2 );

	// keys
	assertx( x.keys() == x );

	// values
	str = "";
	foreach( i, x.values() ) str += i;
	assertx( str == "12hello" );

	// insert
	assert_not_supported( x.insert(2) );
	assert_not_supported( x.insert(2,3) );

	assert_not_supported( x.push_back(2) );
	assert_not_supported( x.pop_back() );
	assert_not_supported( x.push_front(1) );
	assert_not_supported( x.pop_front() );

	assert_not_supported( x.back() );
	assert_not_supported( x.front() );

	// clear
	x.clear();
	assert( !x );

	// contains
	x.extend("a",12)("b","hello");
	assertx( x.contains("a") );
	assertx( x.contains("b") );
	assertx( !x.contains("c") );

	// erase
	x.erase("c");
	x.erase("a");
	assertx( !x.contains("a") );

	// member_inc
	x["a"] = 12;
	++ x["a"];
	assert( x["a"] == 13 );
	x["a"]++;
	assert( x["a"] == 14 );

	// member_dec
	-- x["a"] ;
	assert( x["a"] == 13 );
	x["a"]--;
	assert( x["a"] == 12 );

	// member_add
	x["a"] += 4;
	assert( x["a"] == 16 );

	// member_dec
	x["a"]-=3;
	assert( x["a"] == 13 );

	// member_call done elsewhere

	// mixin
	x += object().extend("c","c_member");
	assert( x["c"] == "c_member" );
	assert( x["a"] == 13 );

	// operator + (after all, if we have +=);
	y = var(object().extend("a",1)) + var(object().extend("b",2));
	assert( y["a"] == 1 );
	assert( y["b"] == 2 );

	// operators
	// ?? Do we really need these tests?
	assert_not_supported( x-null );
	assert_not_supported( x*null );
	assert_not_supported( x/null );
	assert_not_supported( x%null );

	// assignment operators
	assert_not_supported( x-=null );
	assert_not_supported( x*=null );
	assert_not_supported( x/=null );
	assert_not_supported( x%=null );
}


void test_empty_container(var c)
{
	assert( c.empty() );
	assert( c.size() == 0 );
	assert( null == c.front() );
	assert( null == c.back() );
	assert( c.begin() == c.end() );
	assert( c.rbegin() == c.rend() );
}



void test_container_basics(var c)
{
	// A container has a begin() method which returns an iterator to the first item.
	// iterators can be compared for equality
	assert(c.begin() == c.begin());
	assert(c.end() == c.end());

	// The supplied container c should have 3 values, 1, 2 and 3
	assert( c.size() == 3 );

	// Tests begin: we can always dereference an iterator to point
	// to the VALUE, not the pair.
	assert( *c.begin() == 1 );
	assert( c.begin().value() == 1 );

	// Tests comparison of iterators
	assert( std::distance(c.begin(), c.end()) == 3 );

	// Tests front and back
	assert( c.front() == 1 );
	assert( c.back() == 3 );

	// Tests the reverse iterators
	assert( c.rbegin() == c.rbegin() );
	assert( c.rend() == c.rend() );
	assert( *c.rbegin() == 3 );
	assert( c.rbegin().value() == 3 );
	assert( std::distance(c.rbegin(), c.rend()) == 3 );

	// Test decrement
	assert( c.begin() == --++c.begin() );
	assert( c.rbegin() == --++c.rbegin() );

	// Test equality
	assert( c == c );

	// Test clone & clear
	var d = +c;
	d.clear();
	assert( c.size() == 3 );

	test_empty_container(d);

	// Test enumerator
	var e = c.enumerator();
	assert( *e == 1 ); 
	assert( *++e == 2 );
	assert( *++e == 3 );
	assert( !++e );

	var re = c.reverse_enumerator();
	assert( *re == 3 );
	assert( *++re == 2 );
	assert( *++re == 1 );
	assert( !++re );
}


void test_get_negative_array_index()
{
	var x = array()[-1];
}


void test_set_negative_array_index()
{
	array()[-1] = var();
}


var one() 
{ 
	return 1; 
}


void test_array()
{
	// Create an array by calling array() function
	var a1 = array();
	assert( array().as_string() == "[]" );
	assert( array(1).as_string() == "[1]" );
	assert( array(1)(2).as_string() == "[1,2]" );
	assert( array(1,2).as_string() == "[1,2]" );
	assert( array(1)(2)(3).as_string() == "[1,2,3]" );
	assert( array(1,2,3).as_string() == "[1,2,3]" );

	var ca[] = { 1,2,3 };
	assert( array(ca).as_string() == "[1,2,3]" );

	// Convert from another type
	assert( array_from(range(1,3)).as_string() == "[1,2,3]" );

	// Create arrays using array() function
	assertx( lex_equal( array(1,2), array(1)(2) ) );
	assertx( lex_equal( array(1,2,3), array(1)(2)(3) ) );
	assertx( lex_equal( array(1,2,3,4), array(1)(2)(3)(4) ) );
	assertx( lex_equal( array(1,2,3,4,5), array(1)(2)(3)(4)(5) ) );
	assertx( lex_equal( array(1,2,3,4,5,6), array(1)(2)(3)(4)(5)(6) ) );
	assertx( lex_equal( array(1,2,3,4,5,6,7), array(1)(2)(3)(4)(5)(6)(7) ) );
	assertx( lex_equal( array(1,2,3,4,5,6,7,8), array(1)(2)(3)(4)(5)(6)(7)(8) ) );
	assertx( lex_equal( array(1,2,3,4,5,6,7,8,9), array(1)(2)(3)(4)(5)(6)(7)(8)(9) ) );
	assertx( lex_equal( array(1,2,3,4,5,6,7,8,9,10), array(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) ) );

	// Create arrays of fixed size
	var a4 = fill_array(20, "x");
	assert(a4.size() == 20);
	assert(a4[0] == "x");
	assert(a4[19] == "x");
	assert(a4[-1] == "x");

	assert( a4.as_int() == 0 );
	assert( a4.as_double() == 0.0 );
	assert( a4.impl().as_ptr() == 0 );

	assert_throws( not_found, fill_array(-1, "") );

	assert( a1.class_name() == "array" );
	assert( a1.max_args() == 1 );
	assert_throws( not_supported, a1() );

	// The empty array is empty and has zero size
	assert( a1.empty() );
	assert( a1.size() == 0 );

	// array.enumerator
	test_enumerator( array(), 0 );
	test_enumerator( array(1,2,3), 3 );
	assert( lex_equal( array(1,2,3), reverse(array(3,2,1)) ) );

	// array.reserve && capacity
	a1.impl().reserve( 10 );
	assert( a1.impl().capacity() >= 10 );

	// Push an item to the back of the array
	a1.push_back(2);

	// Check the item exists in the array
	assert( !a1.empty() );
	assert( a1.size() == 1 );
	assert( a1[0] == 2 );

	// Pop from the back of an array
	a1.pop_back();
	assert( a1.empty() );

	// Pop back on an empty array does nothing
	a1.pop_back();
	assert( a1.empty() );

	// Getting an invalid item results in null (not invalid_attribute).  Is this right?
	assert( a1[1] == null );

	// Getting a negative attribute
	a1.push_back(100);
	assert(a1[-1] == 100);
	assert(a1[-2] == null);

	// Setting an item negatively throws if it's before the start of the array
	a1[-1] = 12;
	assert( a1[-1] == 12 );
	assert_throws( not_found, a1[-2] = 2);

	// Call an attribute
	a1[0] = var(one);
	assert( a1[0]() == 1 );
	assert( a1["0"]() == 1 );

	// Creating an item beyond the end of the array resizes the array
	// and pads it with null objects
	a1[10] = "fish";
	assert( a1[10] == "fish" );
	assert( a1[5] == null );
	assert( a1.size() == 11 );

	// An array can also be constructed from a pair of iterators
	char str[] = "hello";
	var a2 = array_it(str, str+strlen(str));
	assert( a2[0] == 'h' );
	assert( a2[4] == 'o' );
	assert( var(a2[4]).as_string() == "o" );

	// Equality of arrays
	assert( a2 == a2 );
	assert( lex_equal(a2, +a2) );
	assert( a2 != array() );
	assert( a2 != null );

	{
		var a3 = array();
		a3[0] = "1";
		a3[1] = "2";
		var a4 = array();
		a4[0] = 1;
		a4[1] = 2;
		assert( a3 != a4 );
		assert( a4 != a3 );
	}

	// Inequality of arrays
	{
		char str1[]="hello", str2[]="help";
		var a1 = array_it(str1, str1+5), a2 = array_it(str2, str2+4);
		assert( lex_lt(a1, a2) );
		assert( a1 <= a1 );

		// Comparison on type, not contents
		assert( var("hello") < a1 );
	}

	// array.insert
	assert_not_supported( a1.insert(3) );

	a1 = array(1,2,3);
	a1.insert(1, array(4,5) );
	assertx( a1.as_string() == "[1,4,5,2,3]" );
	a1.insert(-6, array(9,10));
	assertx( a1.as_string() == "[9,10,null,1,4,5,2,3]" );
	a1 = array(2,3);
	a1.insert(4, array(1,2));
	assert( lex_equal( a1, array(2)(3)(null)(null)(1)(2) ) );
	a1 = array(2,3);
	assert_throws( expired_iterator, a1.insert(4,a1) );

	// array.erase
	a1 = array(1,2,3);
	a1.erase(0);
	assertx( a1.as_string() == "[2,3]" );

	a1.erase(range(0,1));
	assert( !a1 );

	a1 = array(1,2,3);
	a1.erase( range(1,10) );
	assert( a1.size() == 1 );
	assert( a1[0] == 1 );
	a1.erase(range(-10,5));

	a1 = array(1,2,3);
	a1.erase(range(10,20));
	assert( a1.size() == 3 );

	a1.erase(range(-1,-2));	// erase 2 elements, -2 from the back
	assert( a1.size() == 1 && a1[0] == 1 );

	a1 = array(1,2,3);
	a1.erase( range_ex(1,1) );
	assert( a1.size() == 3 );

	a1.erase(-1);
	assert( a1.as_string() == "[1,2]" );

	// Resize
	{
		char str1[]="hello";
		var a1 = array_it(str1, str1+5);
		a1.resize(3);
		assert( lex_equal(a1, array_it(str1, str1+3)) );
	}

	// Generic container properties.
	var a3 = array();
	a3[0] = 1;
	a3[1] = 2;
	a3[2] = 3;
	test_container_basics(a3);
	test_empty_container(array());

	// as_bool() returns whether the array is non-empty
	assert( a3 );
	assert( !array() );

	// as_string returns a list of items
	assert( a3.as_string() == "[1,2,3]" );
	assert( a3.as_wstring() == L"[1,2,3]" );

	// array.proxy
	assert( a3.proxy().as_string() == "[1,2,3]" );

	// array.keys
	assert( array(2,3,4).keys() == range(0,2) );

	// array.values
	assert( a3.values() == a3 );

	// array.get_member
	assert( a3[1] == 2 );
	assert( a3["1"] == 2 );
	assert( a3[var(1)] == 2 );
	assert( a3[-1] == 3 );
	assert( a3[10] == null );
	assert( a3[-10] == null );

	// array.set_member
	assert( (a3[1] = 10) == 10 );
	assert( (a3["1"] = 5) == 5 );
	assert( (a3[var(1)] = 2) == 2 );

	// array.push_front
	assert_throws( not_supported, a3.push_front(1) );

	// array.pop_front
	assert_throws( not_supported, a3.pop_front() );

	// array.clear()
	a3.clear();
	assert( !a3 );

	// array.contains
	a3 = array(1,2,3);
	assert(a3.contains(0));
	assert(a3.contains(2));
	assert(!a3.contains(3));
	assert(a3.contains(-1));
	assert(a3.contains(-3));
	assert(!a3.contains(-4));

	// Iterator safety
	assert_throws( expired_iterator, *array().begin() );
	assert_throws( expired_iterator, *array().end() );
	a1 = array(1);
	var ia1 = a1.begin();
	a1.push_back(2);
	assert_throws( expired_iterator, *ia1 );
	++(array().begin());

	std::pair<std::size_t, std::size_t> r;
	assert_not_supported( array().impl().get_range(10,r) );

	// array.add
	assertx( lex_equal(array(1)(2) + array(3)(4), array(1)(2)(3)(4) ) );
	assertx( (array(1)(2) + array(3)(4)).as_string() == "[1,2,3,4]" );
	assertx( !(array() + 5) );	// ?? Is this the right behaviour

	// Other array operators
	var a = array(1,2,3);
	assert_not_supported( a-1 );
	assert_not_supported( a/1 );
	assert_not_supported( a%1 );
	assert( +a != a );
	assert_not_supported ( -a );
	assert_not_supported( a<<1 );
	assert_not_supported( a>>1 );
	assert_not_supported( a&1 );
	assert_not_supported( a|1 );
	assert_not_supported( ~a );

	// array.+=
	a = array(1,2,3);
	a += +a;
	assertx( lex_equal(a, array(1)(2)(3)(1)(2)(3) ) );

	// array.mul
	assertx( lex_equal(array(1,2)*3, array(1)(2)(1)(2)(1)(2) ) );
	assertx( lex_equal(array(1,2)*0, array() ) );

	// array.*=
	a = array(2,3);
	a *= 3;
	assertx( lex_equal( a, array(2)(3)(2)(3)(2)(3) ) );
	a *= 0;
	assertx( !a );

	// array assignment operators
	assert_not_supported( a-=2 );
	assert_not_supported( a/=2 );
	assert_not_supported( a%=2 );
	assert_not_supported( a<<=2 );
	assert_not_supported( a>>=2 );
	assert_not_supported( a&=2 );
	assert_not_supported( a|=2 );
	assert_not_supported( a^=2 );
}


void test_comma_lists()
{
	var l[] = { 1,2,3 };
	assert( var(l).as_string() == "[1,2,3]" );
}


void test_list()
{
	// Construction

	var cl[] = { 1,2,3 };
	assert( list(cl).as_string() == "[1,2,3]" );

	assert( list().as_string() == "[]" );
	assert( list(1).as_string() == "[1]" );
	assert( list(1)(2)(3).as_string() == "[1,2,3]" );
	assert( lex_equal( list_from(array(1)(2)(3)), list(1)(2)(3)) );

	assert( lex_equal( list_it(cl, cl+3), range(1,3) ) );

	var l = list();
	assert( l.empty() );
	assert( l.size() == 0 );
	assert( l.begin() == l.end() );
	assert( l.rbegin() == l.rend() );

	// Construction from list() functions
	assertx( lex_equal( list(1,2), list(1)(2) ) );
	assertx( lex_equal( list(1,2,3), list(1)(2)(3) ) );
	assertx( lex_equal( list(1,2,3,4), list(1)(2)(3)(4) ) );
	assertx( lex_equal( list(1,2,3,4,5), list(1)(2)(3)(4)(5) ) );
	assertx( lex_equal( list(1,2,3,4,5,6), list(1)(2)(3)(4)(5)(6) ) );
	assertx( lex_equal( list(1,2,3,4,5,6,7), list(1)(2)(3)(4)(5)(6)(7) ) );
	assertx( lex_equal( list(1,2,3,4,5,6,7,8), list(1)(2)(3)(4)(5)(6)(7)(8) ) );
	assertx( lex_equal( list(1,2,3,4,5,6,7,8,9), list(1)(2)(3)(4)(5)(6)(7)(8)(9) ) );
	assertx( lex_equal( list(1,2,3,4,5,6,7,8,9,10), list(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) ) );

	// class_name
	assert( l.class_name() == "list" );
	assert( l.impl().shared_var() );

	// call
	assert_not_supported( l() );
	assert( l.max_args() == 1 );

	// as_int
	assert( l.as_int() == 0 );
	assert( l.as_double() == 0.0 );
	assert( !list() );
	assert( list(1) );
	
	// clone
	l=list();
	l.push_back(2);
	var l2=+l;
	assert( l2 != l );
	l2.clear();
	assert( l.back() == 2 );

	// Pop-back etc

	l=list();
	assert( null == l.pop_back() );
	l.push_back(1);
	assert( l.size() == 1 );
	assert( !l.empty() );

	assert(l.pop_back() == 1 );
	assert( l.empty() );

	// iterator stuff
	l.clear();
	l.push_back(1);
	l.push_back(2);
	l.push_front(3);

	// Test string
	assert( l.as_string() == "[3,1,2]" );
	assert( l.as_wstring() == L"[3,1,2]" );
	assert( list().as_string() == "[]" );

	assert( l.back() == 2 );
	assert( l.front() == 3 );
	assert( l.pop_front() == 3 );
	assert( l.pop_back() == 2 );
	assert( l.front() == 1 );
	assert( l.back() == 1 );

	// Test invalid iterators
	l.push_back(2);
	l.push_front(3);

	assert( std::distance(l.begin(), l.end()) == 3 );
	assert( std::distance(l.rbegin(), l.rend()) == 3 );
	assert( *l.begin() == 3 );
	assert( *l.rbegin() == 2 );

	var b = l.begin();
	l.clear();
	assert_throws( expired_iterator, ++b );

	// Test comparator
	char str[]="hello";
	l2 = list_it(str, str+5);
	var l3 = +l2;
	assert( lex_equal(l2, l3) );
	l3.pop_back();
	assert(lex_lt(l3,l2));
	l3.push_back('p');
	assert( lex_lt(l2, l3) );

	// Test pickle
	assert( lex_equal(l3, unpickle(pickle(l3)) ));

	// Test container stuff
	test_container_basics( list(1)(2)(3) );

	// Member access
	assert_not_supported( (var)l[0] );
	assert_not_supported( (l[0]=2) );

	// keys
	assert( l.keys().empty() );
	assert( l.values() == l );

	assert_not_supported( l.insert(1) );

	assert( !l.contains(1) );

	// erase
	l.erase( l.begin() );
	l = list(1)(2)(3);
	l.erase(l.begin());
	l.erase(l.end());
	assert( l.as_string() == "[2,3]" );
	l.erase( ++l.begin() );
	assert( l.as_string() == "[2]" );

	// mixin
	assert_not_supported( l.mixin(null) );

	// operators
	assert( lex_equal( list(1)(2)*2, list(1)(2)(1)(2) ) );
	assert( lex_equal( list(1)(2)+list(3)(4), list(1)(2)(3)(4) ) );
	l = list(1)(2);
	l *= 3;
	assert( l.as_string() == "[1,2,1,2,1,2]" );
	l = list(1)(2);
	l += list(3)(4);
	assert( l.as_string() == "[1,2,3,4]" );
}



void test_map()
{
	// Construction
	var m = map();

	assert( m.class_name() == "map" );
	assert( m != map() );
	assert( m.impl().comparison_index() == cmp_map );

	// as_string
	m[4] = "fish";
	m["haddock"] = 0;
	m["cod"] = 12;
	m[12] = 13;

	assert( m.as_string() == "{4:fish,12:13,cod:12,haddock:0}" );
	assert( m.as_wstring() == L"{4:fish,12:13,cod:12,haddock:0}" );
	assert( map().as_string() == "{}" );

	assert( m.as_int() == 0 );
	assert( m.as_double() == 0.0 );

	// as_bool
	assert( m );
	assert( !map() );

	// call
	assert( map()(1,2)[1] == 2 );
	assert( map(3,4)(5,6)[3] == 4 );
	assert( map(3,4)(5,6)(7,8)[7] == 8 );

	// member
	m[12] = 13;
	assert( m[12] == 13 );

	assert_not_supported( m() );
	assert( m.max_args() == 2 );

	// contains
	assert( m.contains(12) );
	assert( !m.contains("12") );

	// push_back etc
	assert_not_supported( m.push_back(null) );
	assert_not_supported( m.push_front(null) );
	assert_not_supported( m.pop_back() );
	assert_not_supported( m.pop_front() );
	assert_not_supported( m.back() );
	assert_not_supported( m.front() );	

	// clone
	var m1 = map();
		m1["a"] = 12;
		m1["b"] = 13;
		m1["c"] = 14;

	var m2 = +m1;

	// begin, end, enumerator
	assert( lex_equal(m1, m2) );

	m2.erase("c");
	assert( lex_lt(m2, m1) );

	test_enumerator(m1, 3);

	var str="";
	foreach( i, reverse(m1) ) str += i;
	assert( str == "cba" );

	// size
	assert( m1.size() == 3 );
	assert( map().size() == 0 );

	// resize
	assert_not_supported( map().resize(2) );

	// values
	str="";
	foreach( i, m1.values() ) str += i;
	assert( str == "121314" );

	// pickle
	assert( m2.as_string() == unpickle(pickle(m2)).as_string() );
	assert( m1.as_string() == unpickle(pickle(m1)).as_string() );

	// erase
	m[123]=123;
	m.erase(123);
	assert( !m.contains(123) );

	// mixin
	var m3 = map().extend("1",1)("2",2)("3",3);
	var m4 = map().extend("4",4);
	var m5 = m3+m4;
	assert( m5["1"] == 1 );
	assert( m5["4"] == 4 );
	m5.erase(4);
	m5 += m4;
	assert( m5["4"] == 4 );
}



void test_set()
{
	// Construction
	var s = set();

	assert( lex_equal( set_from(range(1,3)), range(1,3) ) );
	char str[] = "abc";
	assert( lex_equal( set_it(str,str+3), "abc" ) );

	assert( s.impl().shared_var() );
	assert( s.class_name() == "set" );
	assert( s < map() );
	assert( s==s );
	assert( s!=+s );
	assert( s.impl().comparison_index() == cmp_set );

	// Test set() functions
	assertx( lex_equal( set(1,2), set(1)(2) ) );
	assertx( lex_equal( set(1,2,3), set(1)(2)(3) ) );
	assertx( lex_equal( set(1,2,3,4), set(1)(2)(3)(4) ) );
	assertx( lex_equal( set(1,2,3,4,5), set(1)(2)(3)(4)(5) ) );
	assertx( lex_equal( set(1,2,3,4,5,6), set(1)(2)(3)(4)(5)(6) ) );
	assertx( lex_equal( set(1,2,3,4,5,6,7), set(1)(2)(3)(4)(5)(6)(7) ) );
	assertx( lex_equal( set(1,2,3,4,5,6,7,8), set(1)(2)(3)(4)(5)(6)(7)(8) ) );
	assertx( lex_equal( set(1,2,3,4,5,6,7,8,9), set(1)(2)(3)(4)(5)(6)(7)(8)(9) ) );
	assertx( lex_equal( set(1,2,3,4,5,6,7,8,9,10), set(1)(2)(3)(4)(5)(6)(7)(8)(9)(10) ) );

	// clone
	var s1 = set(1)(2), s2=+s1;
	s1.erase(1);
	assert( s2.contains(1) );

	// as_bool
	assert( !set() );
	assert( set(1) );

	// as_string
	assert( set(2)(1).as_string() == "{1,2}" );
	assert( set().as_string() == "{}" );
	assert( set(2)(1).as_wstring() == L"{1,2}" );
	assert( set().as_wstring() == L"{}" );

	// pickle/unpickle
	assert( lex_equal( unpickle(pickle(set(1)(2)(3))), array(1)(2)(3) ) );

	// max_args
	assert( s.max_args() == 1 );

	// erase
	s1 = set(1)(2)(3);
	s1.erase(2);
	assert( lex_equal( s1, set(1)(3) ) );

	// contains
	assert( s1.contains(1) );
	assert( !s1.contains(2) );

	// size
	assert( s1.size() == 2 );
	assert( set().size() == 0 );

	// empty
	assert( set().empty() );
	assert( !s1.empty() );

	test_container_basics( set(1)(2)(3) );

	// get_member
	assert_not_supported( s1[1] == null );
	assert_not_supported( s1[1] = null );

	// reverse
	assert( lex_equal( reverse(set(1)(2)(3)), list(3)(2)(1) ) );

	// clear
	s1 = set(1);
	s1.clear();
	assert( !s1 );

	// insert
	s = set();
	s.insert(2);
	s.insert(2);
	assert( s.contains(2) );

	// keys
	s = set("a")(1)(0.2);
	assert( s.keys().empty() );

	// values
	assert( s.values() == s );

	// mixin, operators
	assert( lex_equal( set(1)(2) + set(2)(3), set(1)(2)(3) ) );
	assert( lex_equal( set(1)(2) - set(2)(3), set(1) ) );
	assert( lex_equal( set(1)(2)(3)(4) * set(2)(4)(5), set(2)(4) ) );

	// assignment
	s = set(1)(2)(3);
	s += array(4)(5);
	assert( lex_equal( s, array(1)(2)(3)(4)(5) ) );
	s-=array(2)(1);
	assert( lex_equal(s, array(3)(4)(5) ) );
	s*=set(3)(5)(6);
	assert( lex_equal(s, array(3)(5) ) );

	s.mixin(set(1));
	assert( lex_equal(s, array(1)(3)(5) ) );
}


void test_reverse()
{
	// reverse.constructor
	var items[] = { 1, 2, 3 };
	var ritems = reverse(items);

	// reverse.shared
	assert( ritems.impl().shared_var() );

	assert( ritems.class_name() == "reverse" );
	assert( ritems.impl().comparison_index() == cmp_reverse );
	assert( null < ritems );

	// Clone
	assert( +ritems != ritems );
	assert( (+ritems).as_string() == "reverse([1,2,3])" );

	// as_root, as_nonroot
	assert( ritems == var(object().extend("x", ritems))["x"] );
	assert_throws( not_supported, ritems() );
	assert( ritems.max_args() == -1 );

	// output
	assert( ritems.as_string() == "reverse([1,2,3])" );
	assert( ritems.as_wstring() == L"reverse([1,2,3])" );

	// proxy
	assert( ritems.proxy() != ritems );
	assert( ritems.proxy().as_string() == "reverse([1,2,3])" );

	// pickle
	assert( unpickle(pickle(ritems)).as_string() == "reverse([1,2,3])" );

	assert( ritems.as_int() == 0 );
	assert( ritems.as_double() == 0 );
	assert( ritems );
	assert( !ritems.impl().as_ptr() );

	// begin, end
	assert( std::distance( ritems.begin(), ritems.end() ) == 3 );
	assert( std::distance( ritems.rbegin(), ritems.rend() ) == 3 );

	// enumerator
	var total="";
	foreach( i, ritems ) total += i;
	assert( total == "321" );
	foreach( i, reverse(ritems) ) total += i;
	assert( total == "321123" );

	// size
	assert( ritems.size() == 3 );

	// resize
	assert_throws( not_supported, ritems.resize(2) );

	ritems.impl().reserve(10);
	assert( ritems.impl().capacity() == 0 );

	// empty
	ritems.clear();
	assert( !ritems );
	assert( ritems.empty() );
	ritems = reverse(items);
	assert( !ritems.empty() );

	// keys
	var rkeys = ritems.keys();
	assert( rkeys[0] == 0 );
	assert( rkeys[2] == 2 );
	assert( rkeys.size() == 3 );

	// values
	assert( ritems.values().as_string() == ritems.as_string() );

	// get_member (indexes aren't actually reversed)
	assert( ritems[0] == 1 );
	assert( ritems[2] == 3 );	

	// set_member
	assert_throws( not_supported, ritems[2] = 2 );
	assert_throws( not_supported, ritems.insert(2) );
	assert_throws( not_supported, ritems.insert(2,3) );

	// front, back, push_front, push_back, pop_front, pop_back
	assert( ritems.front() == 3 );
	assert( ritems.back() == 1 );

	assert( ritems.pop_front() == 3 );
	assert( ritems.pop_front() == 2 );
	assert( ritems.pop_front() == 1 );

	var l = list();
	var rlist = reverse(l);
	rlist.push_back(2);
	rlist.push_back(3);
	rlist.push_front(1);
	assert(rlist.back() == 3);
	assert(l.front() == 3);
	assert(rlist.front() == 1);
	assert(l.back() == 1);
	assert( rlist.pop_back() == 3 );

	// contains
	ritems = reverse(items);
	assert( ritems.contains(1) );
	assert( !ritems.contains(3) );

	// erase
	var obj = object().extend("x", 12);
	reverse(obj).erase("x");
	assert( !obj );
}


void assert_sequence_equals(var s0, var s1)
{
	assert( s0.size() == s1.size() );
	for(var i0=s0.begin(), i1=s1.begin(); i0!=s0.end(); ++i0, ++i1)
	{
		assert( *i0 == *i1 );
	}

	for(var i0=s0.enumerator(), i1=s1.enumerator(); i0 && i1; ++i0, ++i1)
	{
		assert( *i0 == *i1 );
	}
}


void test_sequence()
{
	// Constructors
	var s0 = range_ex(0,0);
	var s1 = range(1,3);
	var s2 = range(1,1);
	var s3 = range(2,1);
	var s4 = range_ex(2,1);
	var s5 = sequence(10,10);
	var s6 = sequence(10,5,2);

	// sequence.size
	assert( s0.size() == 0 );
	assert( !s0 );
	assert( s1.size() == 3 );
	assert( s1 );
	assert( s2.size() == 1 );
	assert( s3.size() == 2 );
	assert( s4.size() == 1 );
	assert( s5.size() == 10 );
	assert( s6.size() == 5 );

	// sequence.class_name
	assert( s0.class_name() == "sequence" );

	// sequence.shared
	assert( s0.impl().shared_var() );

	// sequence.comparison_index
	assert( s0.impl().comparison_index() == cmp_sequence );

	// sequence.max_args
	assert( s0.max_args() == -1 );

	// sequence.comparison
	assert( s1 > null );
	assert( s1 == s1 );
	assert( range(1,1) == range_ex(1,2) );
	assert( range(1,2) < range(1,3) );
	assert( range(1,10) < range(2,4) );
	assert( sequence(1,0,-1) == sequence(1,0,4) );
	assert( range(1,1) == sequence(1,1,1) );
	assert( range(2,1) == sequence(2,2,-1) );
	assert( range_ex(4,4) == sequence(4,0) );
	assert( range_ex(4,3) == sequence(4,1,-1) );
	assert( sequence(10,10) == sequence(10,10,1) );

	// sequence.clone
	assert( s1 == +s1 );
	assert( +s3 == s3 );
	(+s3).resize(100);
	assert( s3.size() == 2 );

	// sequence.as_root
	var o = object();
	o["x"] = s1;
	assert( o["x"] == s1 );

	// sequence.output
	assert( range(1,4).as_string() == "sequence(1,4,1)" );
	assert( range(1,4).as_wstring() == L"sequence(1,4,1)" );
	assert( range_ex(4,2).as_string() == "sequence(4,2,-1)" );
	assert( range_ex(4,2).as_wstring() == L"sequence(4,2,-1)" );
	assert( sequence(10,2,3).as_string() == "sequence(10,2,3)" );

	// sequence.proxy
	assert( range(1,2).proxy().as_string() == "sequence(1,2,1)" );

	// sequence.pickle
	assert( unpickle(pickle(s1)) == s1 );
	assert( unpickle(pickle(s6)) == s6 );

	// sequence.call
	assert_throws( not_supported, s0() );

	// sequence.as_int
	assert( range(4,2).as_int() == 4 );

	// sequence.as_double
	assert( range(4,2).as_double() == 4.0 );

	// sequence.as_bool
	assert( range(1,1) );
	assert( !range_ex(1,1) );

	// sequence.as_ptr
	assert( !s0.impl().as_ptr() );

	// sequence.begin, end, rbegin, rend
	assert_sequence_equals( range(1,2), reverse(range(2,1)) );
	assert_sequence_equals( sequence(5, 4, 3), reverse(sequence(14,4,-3)) );
	assert( std::distance( s5.begin(), s5.end() ) == 10 );

	assert_throws( not_supported, *s0 );
	assert_throws( not_supported, s0.key() );
	assert_throws( not_supported, s0.value() );

	// sequence.enumerator
	test_enumerator(range(1,3), 3);
	test_container_basics(range(1,3));

	s0.impl().reserve(10);
	assert( s0.impl().capacity() == 0 );

	// sequence.empty
	assert( range_ex(1,1).empty() );
	assert( sequence(100, 0, 10).empty() );
	assert( !range(1,2).empty() );

	// sequence.get_member
	assert( range(1,10)[0] == 1 );
	assert( sequence(4,3,2)[3] == 10 );
	assert( sequence(4,5,2)[-2] == 10 );

	// sequence.set_member
	assert_throws( not_supported, range(1,10)[1] = 2 );

	// sequence.resize
	s0 = range(1,10);
	s0.resize(15);
	assert( s0 == range(1,15) );

	// sequence.insert
	assert_throws( not_supported, s0.insert(1) );
	assert_throws( not_supported, s0.insert(null,null) );

	// sequence.keys
	assert( range(1,10).keys() == range(0,9) );
	assert( sequence(5,3,-1).keys() == range_ex(0,3) );
	assert( range_ex(0,10).keys() == range_ex(0,10) );

	// sequence.values
	assert( range(1,10).values() == range(1,10) );

	// sequence.push_back
	assert_throws( not_supported, s0.push_back(var()) );

	// sequence.push_front
	assert_throws( not_supported, s0.push_front(var()) );

	s0 = sequence(2,3,4);
	s1 = sequence(10,0);

	// sequence.back
	assert( s0.back() == 10 );
	assert( s1.back() == var() );

	// sequence.front
	assert( s0.front() == 2 );
	assert( s1.front() == var() );

	// sequence.pop_front
	assert( s0.pop_front() == 2 );
	assert( s0 == sequence(6,2,4) );
	assert( s1.pop_front() == var() );
	assert( s1 == sequence(10,0) );

	// sequence.pop_back
	assert( s0.pop_back() == 10 );
	assert( s0 == sequence(6,1,4) );
	assert( s1.pop_front() == var() );
	assert( s1 == sequence(10,0) );

	// sequence.clear
	s0.clear();
	assert( s0 == sequence(6,0) );

	// sequence.contains
	s0 = range(1,10);
	assert( s0.contains(0) );	// Illogical - it contains the key 0
	assert( s0.contains(9) );
	assert( !s0.contains(10) );
	assert( !s0.contains(-1) );

	// sequence.erase	
	s0.erase(12);

	// sequence.member_inc
	assert_throws( not_supported, ++s0["test"] );
	assert_throws( not_supported, --s0["test"] );
	assert_throws( not_supported, s0["test"]+=2 );
	assert_throws( not_supported, s0["test"]-=4 );

	// sequence.member_call
	assert_throws( not_supported, s0["1"]() );

	// sequence.mixin
	assert_throws( not_supported, s0.mixin(s1) );

	// sequence.iterator
	assert_throws( not_supported, s0.key() );
	assert_throws( not_supported, s0.value() );
	assert_throws( not_supported, *s0 );

	// sequence.op_inc
	assert( ++sequence(12,2) == sequence(13,2) );

	// sequence.op_dec
	assert( --range(5,6) == range(4,5) );

	// sequence.op_add
	assert( range(3,5)+5 == range(8,10) );

	// sequence.op_sub
	assert( range(3,5)-2 == range(1,3) );

	// sequence.op_mul
	assert( range(2,3)*4 == range(2,9) );

	// sequence.op_*
	assert_throws( not_supported, s0/2 );
	assert_throws( not_supported, s0%2 );
	assert_throws( not_supported, -s0 );
	assert_throws( not_supported, s0&1 );
	assert_throws( not_supported, s0|1 );
	assert_throws( not_supported, s0^1 );
	assert_throws( not_supported, ~s0 );

	s0 = range(2,4);

	// sequence.+=
	assert( (s0 += 3) == range(5,7) );

	// sequence.-=
	assert( (s0 -= 2) == range(3,5) );

	// sequence.*=
	assert( (s0 *= 3) == range(3,11) );

	// sequence.assign
	assert_throws( not_supported, s0 /= 2 );
	assert_throws( not_supported, s0 %= 2 );
	assert_throws( not_supported, s0 <<= 2 );
	assert_throws( not_supported, s0 >>= 2 );
	assert_throws( not_supported, s0 &= 2 );
	assert_throws( not_supported, s0 |= 2 );
	assert_throws( not_supported, s0 ^= 2 );
}


void counter_set(var counter, var name, var value)
{
	counter[name] = value;
}


var counter_get(var counter, var name)
{
	var result = counter.contains(name) ? counter[name] : var(0);
	counter[name] = result+1;
	return result;
}


var get_rnd(var object, var member)
{
	return object["seed"] * 1664525 + 1013904223;
}


var echo(var x) 
{ 
	return x; 
}


var constant(var x) 
{ 
	return bind(echo,x); 
}


var record_param(var observer, var value) 
{ 
	observer["value"]=value; 
	return value; 
}


var record_param2(var observer, var v1, var v2) 
{ 
	observer["value1"] = v1; 
	observer["value2"]=v2; 
	return v2; 
}


var mock(var observer, var return_val, var input)
{
	observer["value"] = input;
	return return_val;
}


enable_pickle(counter_get);
enable_pickle(counter_set);
enable_pickle(get_rnd);
enable_pickle(echo)
enable_pickle(constant)
enable_pickle(record_param)
enable_pickle(record_param2)
enable_pickle(mock)


void test_dispatcher()
{
	var observer = object();
	var record_one_param = bind(record_param, observer);
	var record_two_params = bind(record_param2, observer);

	// Construction
	var o = object().extend
		("seed", 0)
		("get_member", get_rnd)
		("class_name", constant("class_name"))
		("call", varargs(echo))
		("as_string", constant("as_string"))
		("as_int", constant(123))
		("as_double", constant(0.125))
		("as_bool", constant(true))
		("enumerator", constant("enumerator"))
		("reverse_enumerator", constant("reverse_enumerator"))
		("size", constant(10))
		("empty", constant(false))
		("keys", constant("keys"))
		("values", constant("values"))
		("reserve", record_one_param)
		("insert", record_one_param)
		("capacity", constant(9))
		("set_member", record_two_params)
		("get_member", var(echo))
		;

	var d = dispatcher(o);

	// Shared
	assert( d.impl().shared_var() );

	// Class name
	assert( d.class_name() == "class_name" );

	// Compare
	assert( d == d );
	assert( d > 2 );
	assert( d != dispatcher(o) );
	assert( d.impl().comparison_index() == cmp_dispatcher );

	// Clone
	o["clone"] = constant(3);
	assert( d.clone() == 3 );

	// Pickle
	var d2 = unpickle(pickle(d));
	assert( d2.clone() == 3 );
	assert( d2.impl().capacity() == 9 );

	// Call
	assert( lex_equal( d(),  array() ) );
	assert( lex_equal( d(1),  array(1) ) );
	assert( lex_equal( d(1,2),  array(1)(2) ) );
	assert( lex_equal( d(1,2,3),  array(1)(2)(3) ) );
	assert( lex_equal( d(1,2,3,4),  array(1)(2)(3)(4) ) );
	assert( lex_equal( d(1,2,3,4,5),  array(1)(2)(3)(4)(5) ) );
	assert( lex_equal( d(1,2,3,4,5,6),  array(1)(2)(3)(4)(5)(6) ) );
	assert( lex_equal( d(1,2,3,4,5,6,7),  array(1)(2)(3)(4)(5)(6)(7) ) );
	assert( lex_equal( d(1,2,3,4,5,6,7,8),  array(1)(2)(3)(4)(5)(6)(7)(8) ) );
	assert( lex_equal( d(1,2,3,4,5,6,7,8,9),  array(1)(2)(3)(4)(5)(6)(7)(8)(9) ) );
	assert( lex_equal( d(1,2,3,4,5,6,7,8,9,0),  array(1)(2)(3)(4)(5)(6)(7)(8)(9)(0) ) );

	assert( d.max_args() == varargs(echo).max_args() );

	assert( d.as_string() == "as_string" );
	assert( d.as_wstring() == L"as_string" );

	assert( d.as_int() == 123 );
	assert( d.as_double() == 0.125 );
	assert( d ); // as_bool

	// Container
	assert( d.size() == 10 );
	assert( d.enumerator() == "enumerator" );
	assert( d.reverse_enumerator() == "reverse_enumerator" );
	assert( !d.empty() );
	assert( d.keys() == "keys" );
	assert( d.values() == "values" );

	d.insert(12);
	assert( observer["value"] == 12 );

	d.impl().reserve(13);
	assert( observer["value"] == 13 );
	assert( d.impl().capacity() == 9 );

	// resize
	o["resize"] = record_one_param;
	d.resize(10);
	assert( observer["value"] == 10 );

	// get_member
	assert( d["fish"] == "fish" );
	d["hello"]=10;
	assert( observer["value1"] == "hello" );
	assert( observer["value2"] == 10 );

	// push_back
	o["push_back"] = record_one_param;
	d.push_back("push_back");
	assert(observer["value"] == "push_back" );

	// push_front
	o["push_front"] = record_one_param;
	d.push_back("push_front");
	assert(observer["value"] == "push_front" );

	// back
	o["back"] = constant("back");
	assert( d.back() == "back" );

	// front
	o["front"] = constant("front");
	assert( d.front() == "front" );

	// pop_back
	o["pop_back"] = constant("pop_back");
	assert (d.pop_back() == "pop_back");

	// pop_front
	o["pop_front"] = constant("pop_front");
	assert (d.pop_front() == "pop_front");

	// clear
	o["clear"] = bind(record_one_param, "clear");
	d.clear();
	assert( observer["value"] == "clear" );

	// contains
	o["contains"] = record_one_param;
	assert( !d.contains(false) );
	assert( observer["value"] == false );
	assert( !d.contains("") );
	assert( observer["value"] == "" );
	assert( d.contains(12) );
	assert( observer["value"] == 12 );

	// erase
	o["erase"] = record_one_param;
	d.erase(13);
	assert( observer["value"] == 13 );

	// key
	o["key"] = constant("key");
	assert( d.key() == "key" );

	// value
	o["value"] = constant("value");
	assert( d.value() == "value" );

	// deref
	o["deref"] = constant("DEREF");
	assert( *d == "DEREF" );
	
	// ++
	o["++"] = bind( record_one_param, "inc" );
	++d;
	assert( observer["value"] == "inc" );

	// --
	o["--"] = bind( record_one_param, "dec" );
	--d;
	assert( observer["value"] == "dec" );

	// Operators
	o["+"] = bind(mock, observer, "+");
	assert( d + 2 == "+" );
	assert( observer["value"] == 2 );

	o["-"] = bind(mock, observer, "-");
	assert( d - 3 == "-" );
	assert( observer["value"] == 3 );

	o["*"] = bind(mock, observer, "*");
	assert( d * 4 == "*" );
	assert( observer["value"] == 4 );

	o["/"] = bind(mock, observer, "/");
	assert( d / 5 == "/" );
	assert( observer["value"] == 5 );

	o["%"] = bind(mock, observer, "%");
	assert( d % 0 == "%" );
	assert( observer["value"] == 0 );

	o["neg"] = bind(mock, observer, "-", 1);
	assert( -d == "-" );
	assert( observer["value"] == 1 );

	o["<<"] = bind(mock, observer, "<<");
	assert( d<<3 == "<<" );
	assert( observer["value"] == 3 );

	o[">>"] = bind(mock, observer, ">>");
	assert( d>>4 == ">>" );
	assert( observer["value"] == 4 );

	o["&"] = bind(mock, observer, "&");
	assert( (d&1) == "&" );
	assert( observer["value"] == 1 );

	o["|"] = bind(mock, observer, "|");
	assert( (d|2) == "|" );
	assert( observer["value"] == 2 );

	o["^"] = bind(mock, observer, "^");
	assert( (d^3) == "^" );
	assert( observer["value"] == 3 );

	o["~"] = bind(mock, observer, "~", 0);
	assert( ~d == "~" );
	assert( observer["value"] == 0 );

	// Assignment
	o["+="] = record_one_param;
	d+=2;
	assert( observer["value"] == 2 );

	o["-="] = record_one_param;
	d-=3;
	assert( observer["value"] == 3 );

	o["*="] = record_one_param;
	d*=4;
	assert( observer["value"] == 4 );

	o["/="] = record_one_param;
	d/=1;
	assert( observer["value"] == 1 );

	o["%="] = record_one_param;
	d%=2;
	assert( observer["value"] == 2 );

	o["<<="] = record_one_param;
	d<<=1;
	assert( observer["value"] == 1 );

	o[">>="] = record_one_param;
	d>>=2;
	assert( observer["value"] == 2 );

	o["&="] = record_one_param;
	d&=3;
	assert( observer["value"] == 3 );
 
	o["|="] = record_one_param;
	d|=4;
	assert( observer["value"] == 4 );

	o["^="] = record_one_param;
	d^=3;
	assert( observer["value"] == 3 );

	// Another example
	// Implement an auto-incrementing counter: each time you read a value
	// its value goes up.
	var counter = dispatcher( map().extend
		("get_member", counter_get)
		("set_member", counter_set) );

	assert( counter["champ"] == 0 );
	assert( counter["champ"] == 1 );
	counter["champ"] = 10;
	assert( counter["champ"] == 10 );
	assert( counter["champ"] == 11 );
	counter[6]=4;
	assert( counter[6] == 4 );
	assert( counter[6] == 5 );
	assert( counter["champ"] ==12 );
}


var is_even(var e) 
{ 
	return e%2==0; 
}


var not_(var p, var i) 
{ 
	return !p(i); 
}


var double_(var i) 
{ 
	return i*2; 
}

enable_pickle(is_even)
enable_pickle(double_)


void test_filter()
{
	// Construction
	var f = filter(range(1,10), is_even);

	assert( f.impl().shared_var() );
	assert( f.class_name() == "filter" );
	assert( f.impl().comparison_index() == cmp_filter );
	assert( f > 1 );
	assert( f != +f );
	assert( (+f).as_string() == f.as_string() );

	assert( f.max_args() == -1 );

	assert( f.as_string() == "filter(sequence(1,10,1))" );
	assert( f.as_wstring() == L"filter(sequence(1,10,1))" );

	assert( unpickle(pickle(f)).as_string() == f.as_string());

	assert( f.size() == 0 );

	// Enumerator
	var e = f.enumerator();
	assert(e);
	assert( *e == 2 );
	assert( *++e == 4 );
	assert( *++e == 6 );
	assert( *++e == 8 );
	assert(e);
	assert( *++e == 10 );
	assert( !++e );

	// reverse_enumerator
	e = f.reverse_enumerator();
	assert(e);
	assert( *e == 10 );
	assert( *++e == 8 );
	assert( *++e == 6 );
	assert( *++e == 4 );
	assert( *++e == 2 );
	++e;
	assert( !e );

	foreach( i, filter(null, is_even)) assertx(0);

}


void test_transform()
{
	// Construction
	var t = transform(range(1,10), double_);

	assert( t.impl().shared_var() );
	assert( t.class_name() == "transform" );
	assert( t.impl().comparison_index() == cmp_transform );
	assert( t > "" );
	assert( t != +t );
	assert( t.as_string() == (+t).as_string() );

	assert( t.max_args() == -1 );

	assert( t.as_string() == "transform(sequence(1,10,1))" );
	assert( t.as_wstring() == L"transform(sequence(1,10,1))" );

	assert( unpickle(pickle(t)).as_string() == t.as_string());

	assert( t.size() == 10 );
	assert( t );
	assert( !range(1,10).empty() );
	assert( !t.empty() );

	// enumerator
	var t2 = transform( range(1,3), double_ );
	var e = t2.enumerator();
	assert(e);
	assert( *e == 2 );
	assert( *++e == 4 );
	assert( *++e == 6 );
	assert(e);
	assert( !++e);

	// reverse_enumerator
	e = t2.reverse_enumerator();
	assert(e);
	assert( *e==6 );
	assert( *++e == 4 );
	assert( *++e == 2 );
	assert( e );
	assert( !++e );

	// general test
	foreach( i, transform( filter( range(1,3), is_even ), double_ ) )
		assert( i == 4 );

	foreach( i, filter( transform(range(1,2), double_), is_even) )
		assert( i==2 || i==4 );

	// front
	assert( t2.front() == 2 );

	// back
	assert( t2.back() == 6 );
}


void test_tail()
{
	// Construction
	var t0 = tail(array());
	var t1 = tail(array(1));
	var t2 = tail(array(1)(2)(3));

	// class_name
	assertx( t0.class_name() == "tail" );

	// shared
	assert( t1.impl().shared_var() );

	// compare
	assertx( t0 == t0 );
	assertx( t0 > 0 );
	assertx( t0 != t1 );

	// clone
	assert( +t0 != t0 );
	assert( (+t0).as_string() == t0.as_string() );

	// output
	assertx( t0.as_string() == "tail([])" );
	assertx( t1.as_wstring() == L"tail([1])" );

	// enumerator/iterator
	assertx( lex_equal( array(2)(3), t2) );
	assertx( lex_equal( array(), t1 ) );
	assertx( lex_equal( array(), t0 ) );

	var total=0;
	foreach( i, t2 ) total+=i;
	assertx( total == 5 );

	// proxy
	assertx( unpickle(pickle(t2)).as_string() == t2.as_string() );
	
	// size
	assertx( t0.size() == 0 );
	assertx( t1.size() == 0 );
	assertx( t2.size() == 2 );

	// empty
	assertx( t0.empty() );
	assertx( t1.empty() );
	assertx( !t2.empty() );

	// front()
	assertx( tail(array(1)(2)(3)).front() == 2 );

	// back()
	assertx( tail(array(1)(2)(3)).back() == 3 );

	// as_bool
	assertx( !t0 );
	assertx( !t1 );
	assertx( t2 );
}
